import os
import tkinter as tk
from tkinter import filedialog, messagebox, scrolledtext

import ttkbootstrap as ttkb
from ttkbootstrap.constants import *

from app.util.annotation_util import ClassInfo, annotate
from app.view.base_view import View


@annotate(ClassInfo(name="BinaryView", category="file"))
class BinaryView(View):
    def __init__(self, parent, controller):
        super().__init__(parent, controller)

        # 当前打开的文件路径
        self.current_file = None
        self.file_content = b""

        # 显示设置
        self.byte_group_size = 1  # 1: 单字节, 2: 双字节, 4: 四字节
        self.bytes_per_line = 16  # 每行显示的字节数
        self.current_font_size = 10
        self.current_font_family = "Consolas"

        # 查找相关变量
        self.find_window = None
        self.find_entry = None
        self.find_type = tk.StringVar(value="hex")  # hex 或 ascii
        self.find_pos = 0

        # 创建UI
        self._create_widgets()

        # 绑定事件
        self.bind_events()

    def _create_widgets(self):
        # 创建工具栏
        self.create_toolbars()

        # 创建主内容区域
        self.create_main_content()

        # 创建状态栏
        self.create_status_bar()

    def create_toolbars(self):
        # 主工具栏框架
        toolbar_frame = ttkb.Frame(self)
        toolbar_frame.pack(side=TOP, fill=X, padx=5, pady=5)

        # 文件操作工具栏
        file_toolbar = ttkb.LabelFrame(toolbar_frame, text="文件操作", padding=5)
        file_toolbar.pack(side=LEFT, padx=5)

        ttkb.Button(file_toolbar, text="打开", command=self.open_file, bootstyle=OUTLINE).pack(side=LEFT, padx=2)
        ttkb.Button(file_toolbar, text="保存", command=self.save_file, bootstyle=OUTLINE).pack(side=LEFT, padx=2)
        ttkb.Button(file_toolbar, text="另存为", command=self.save_as_file, bootstyle=OUTLINE).pack(side=LEFT, padx=2)
        # ttkb.Button(file_toolbar, text="退出", command=self.quit, bootstyle=DANGER).pack(side=LEFT, padx=2)

        # 查看设置工具栏
        view_toolbar = ttkb.LabelFrame(toolbar_frame, text="查看设置", padding=5)
        view_toolbar.pack(side=LEFT, padx=5)

        ttkb.Label(view_toolbar, text="显示模式:").pack(side=LEFT, padx=2)
        self.byte_group_var = tk.IntVar(value=self.byte_group_size)
        ttkb.Radiobutton(view_toolbar, text="单字节", variable=self.byte_group_var,
                         value=1, command=self.change_byte_group).pack(side=LEFT, padx=2)
        ttkb.Radiobutton(view_toolbar, text="双字节", variable=self.byte_group_var,
                         value=2, command=self.change_byte_group).pack(side=LEFT, padx=2)
        ttkb.Radiobutton(view_toolbar, text="四字节", variable=self.byte_group_var,
                         value=4, command=self.change_byte_group).pack(side=LEFT, padx=2)

        # 字体设置工具栏
        font_toolbar = ttkb.LabelFrame(toolbar_frame, text="字体设置", padding=5)
        font_toolbar.pack(side=LEFT, padx=5)

        ttkb.Label(font_toolbar, text="字号:").pack(side=LEFT, padx=2)
        self.font_size_var = tk.IntVar(value=self.current_font_size)
        ttkb.Button(font_toolbar, text="-", command=self.decrease_font_size,
                    bootstyle=OUTLINE, width=2).pack(side=LEFT)
        ttkb.Label(font_toolbar, textvariable=self.font_size_var, width=3).pack(side=LEFT, padx=1)
        ttkb.Button(font_toolbar, text="+", command=self.increase_font_size,
                    bootstyle=OUTLINE, width=2).pack(side=LEFT)

        # 搜索和跳转工具栏
        search_toolbar = ttkb.LabelFrame(toolbar_frame, text="搜索与跳转", padding=5)
        search_toolbar.pack(side=LEFT, padx=5)

        ttkb.Button(search_toolbar, text="查找", command=self.find_text, bootstyle=OUTLINE).pack(side=LEFT, padx=2)

        ttkb.Label(search_toolbar, text="跳转到行:").pack(side=LEFT, padx=2)
        self.goto_line_var = tk.StringVar()
        ttkb.Entry(search_toolbar, textvariable=self.goto_line_var, width=6).pack(side=LEFT, padx=2)
        ttkb.Button(search_toolbar, text="跳转", command=self.goto_line, bootstyle=OUTLINE).pack(side=LEFT, padx=2)

        # 主题选择工具栏
        theme_toolbar = ttkb.LabelFrame(toolbar_frame, text="主题", padding=5)
        theme_toolbar.pack(side=LEFT, padx=5)

        self.selected_theme = tk.StringVar(value="darkly")
        theme_combobox = ttkb.Combobox(
            theme_toolbar,
            textvariable=self.selected_theme,
            values=ttkb.Style().theme_names(),
            width=12,
            state="readonly"
        )
        theme_combobox.pack(side=LEFT, padx=2)
        theme_combobox.bind("<<ComboboxSelected>>", lambda e: self.change_theme())

    def create_main_content(self):
        # 创建主框架
        main_frame = ttkb.Frame(self)
        main_frame.pack(fill=BOTH, expand=True, padx=5, pady=5)

        # 行号区域
        self.line_numbers = tk.Text(main_frame, width=8, padx=5, takefocus=0, border=0,
                                    state=tk.DISABLED,
                                    font=(self.current_font_family, self.current_font_size))
        self.line_numbers.pack(side=LEFT, fill=Y)

        # Hex显示区域
        hex_frame = ttkb.LabelFrame(main_frame, text="Hex", padding=5)
        hex_frame.pack(side=LEFT, fill=BOTH, expand=True)

        self.hex_area = scrolledtext.ScrolledText(
            hex_frame,
            wrap=tk.NONE,
            font=(self.current_font_family, self.current_font_size),
            state=tk.DISABLED
        )
        self.hex_area.pack(fill=BOTH, expand=True)

        # ASCII显示区域
        ascii_frame = ttkb.LabelFrame(main_frame, text="ASCII", padding=5)
        ascii_frame.pack(side=LEFT, fill=BOTH, expand=True)

        self.ascii_area = scrolledtext.ScrolledText(
            ascii_frame,
            wrap=tk.NONE,
            font=(self.current_font_family, self.current_font_size),
            state=tk.DISABLED,
            width=20
        )
        self.ascii_area.pack(fill=BOTH, expand=True)

        # 同步滚动
        self.hex_area.bind("<MouseWheel>", self.sync_scroll)
        self.ascii_area.bind("<MouseWheel>", self.sync_scroll)
        self.line_numbers.bind("<MouseWheel>", self.sync_scroll)

    def create_status_bar(self):
        self.status_bar = ttkb.Label(self, text="未打开文件", anchor=W)
        self.status_bar.pack(side=BOTTOM, fill=X)

    def bind_events(self):
        # 绑定快捷键
        self.bind("<Control-o>", lambda event: self.open_file())
        self.bind("<Control-s>", lambda event: self.save_file())
        self.bind("<Control-Shift-S>", lambda event: self.save_as_file())
        self.bind("<Control-f>", lambda event: self.find_text())
        self.bind("<Control-g>", lambda event: self.goto_line())
        # 字体大小快捷键
        # self.bind("<Control-Plus>", lambda event: self.increase_font_size())
        # self.bind("<Control-Minus>", lambda event: self.decrease_font_size())

    def sync_scroll(self, event):
        """同步三个区域的滚动"""
        self.hex_area.yview_scroll(int(-event.delta / 120), "units")
        self.ascii_area.yview_scroll(int(-event.delta / 120), "units")
        self.line_numbers.yview_scroll(int(-event.delta / 120), "units")

    def update_display(self):
        """更新显示内容"""
        if not self.file_content:
            self.clear_displays()
            return

        # 清空显示区域
        self.hex_area.config(state=tk.NORMAL)
        self.hex_area.delete("1.0", tk.END)

        self.ascii_area.config(state=tk.NORMAL)
        self.ascii_area.delete("1.0", tk.END)

        self.line_numbers.config(state=tk.NORMAL)
        self.line_numbers.delete("1.0", tk.END)

        # 计算总字节数和行数
        total_bytes = len(self.file_content)
        total_lines = (total_bytes + self.bytes_per_line - 1) // self.bytes_per_line

        # 填充内容
        for line in range(total_lines):
            # 计算当前行的偏移量
            offset = line * self.bytes_per_line

            # 添加行号（十六进制偏移量）
            self.line_numbers.insert(tk.END, f"{offset:08X}\n")

            # 获取当前行的字节
            line_bytes = self.file_content[offset:offset + self.bytes_per_line]

            # 处理Hex显示
            hex_text = ""
            group_size = self.byte_group_size

            for i in range(0, len(line_bytes), group_size):
                # 按组大小取字节
                group = line_bytes[i:i + group_size]

                # 根据字节顺序转换（小端）
                if group_size > 1:
                    group = group[::-1]  # 反转字节顺序

                # 转换为十六进制字符串
                hex_group = ''.join(f"{b:02X}" for b in group)
                hex_text += hex_group + " "

            # 确保每行对齐
            hex_text = hex_text.ljust((self.bytes_per_line // group_size) * (2 * group_size + 1))
            self.hex_area.insert(tk.END, hex_text + "\n")

            # 处理ASCII显示
            ascii_text = ""
            for b in line_bytes:
                # 可打印字符显示本身，不可打印字符显示点
                ascii_char = chr(b) if 32 <= b <= 126 else '.'
                ascii_text += ascii_char

            self.ascii_area.insert(tk.END, ascii_text + "\n")

        # 恢复只读状态
        self.hex_area.config(state=tk.DISABLED)
        self.ascii_area.config(state=tk.DISABLED)
        self.line_numbers.config(state=tk.DISABLED)

        # 更新状态栏
        self.update_status()

    def clear_displays(self):
        """清空所有显示区域"""
        self.hex_area.config(state=tk.NORMAL)
        self.hex_area.delete("1.0", tk.END)
        self.hex_area.config(state=tk.DISABLED)

        self.ascii_area.config(state=tk.NORMAL)
        self.ascii_area.delete("1.0", tk.END)
        self.ascii_area.config(state=tk.DISABLED)

        self.line_numbers.config(state=tk.NORMAL)
        self.line_numbers.delete("1.0", tk.END)
        self.line_numbers.config(state=tk.DISABLED)

    def update_status(self):
        """更新状态栏信息"""
        if not self.current_file:
            self.status_bar.config(text="未打开文件")
            return

        total_bytes = len(self.file_content)
        file_name = os.path.basename(self.current_file)
        self.status_bar.config(
            text=f"文件: {file_name} | 大小: {total_bytes} 字节 | 显示模式: {self.byte_group_size}字节")

    def open_file(self):
        """打开二进制文件"""
        file_path = filedialog.askopenfilename(
            filetypes=[("所有文件", "*.*"), ("二进制文件", "*.bin;*.dat"),
                       ("可执行文件", "*.exe;*.dll"), ("文本文件", "*.txt")]
        )

        if file_path:
            try:
                with open(file_path, "rb") as file:
                    self.file_content = file.read()

                self.current_file = file_path
                # self.title((f"二进制文件查看器 - {os.path.basename(file_path)}")
                self.update_display()
            except Exception as e:
                messagebox.showerror("错误", f"无法打开文件: {str(e)}")

    def save_file(self):
        """保存文件"""
        if self.current_file:
            try:
                with open(self.current_file, "wb") as file:
                    file.write(self.file_content)
                messagebox.showinfo("提示", "文件已保存")
                return True
            except Exception as e:
                messagebox.showerror("错误", f"无法保存文件: {str(e)}")
                return False
        else:
            return self.save_as_file()

    def save_as_file(self):
        """另存为文件"""
        file_path = filedialog.asksaveasfilename(
            defaultextension=".bin",
            filetypes=[("二进制文件", "*.bin"), ("所有文件", "*.*")]
        )

        if file_path:
            self.current_file = file_path
            return self.save_file()
        return False

    def change_byte_group(self):
        """更改字节分组显示模式"""
        self.byte_group_size = self.byte_group_var.get()
        self.update_display()

    def increase_font_size(self):
        """增大字体大小"""
        if self.current_font_size < 20:  # 最大字号限制
            self.current_font_size += 1
            self.font_size_var.set(self.current_font_size)
            self.update_font()

    def decrease_font_size(self):
        """减小字体大小"""
        if self.current_font_size > 8:  # 最小字号限制
            self.current_font_size -= 1
            self.font_size_var.set(self.current_font_size)
            self.update_font()

    def update_font(self):
        """更新字体设置"""
        new_font = (self.current_font_family, self.current_font_size)
        self.hex_area.configure(font=new_font)
        self.ascii_area.configure(font=new_font)
        self.line_numbers.configure(font=new_font)

    def goto_line(self):
        """跳转到指定行"""
        try:
            line_num = int(self.goto_line_var.get())
            if line_num < 1:
                raise ValueError

            # 计算总行数
            total_lines = (len(self.file_content) + self.bytes_per_line - 1) // self.bytes_per_line

            if line_num > total_lines:
                messagebox.showinfo("提示", f"超出范围，最大行数为 {total_lines}")
                return

            # 滚动到指定行
            self.hex_area.see(f"{line_num}.0")
            self.ascii_area.see(f"{line_num}.0")
            self.line_numbers.see(f"{line_num}.0")

            # 清空输入框
            self.goto_line_var.set("")

        except ValueError:
            messagebox.showerror("错误", "请输入有效的行号")

    def find_text(self):
        """查找功能"""
        # 如果查找窗口已存在，则激活它
        if self.find_window and self.find_window.winfo_exists():
            self.find_window.lift()
            self.find_entry.focus_set()
            return

        # 创建查找窗口
        self.find_window = ttkb.Toplevel(self)
        self.find_window.title("查找")
        self.find_window.geometry("400x150")
        self.find_window.resizable(False, False)
        self.find_window.transient(self)  # 设置为主窗口的子窗口
        self.find_window.grab_set()  # 模态窗口

        # 创建查找框
        frame = ttkb.Frame(self.find_window, padding=10)
        frame.pack(fill=BOTH, expand=True)

        # 查找类型选择
        type_frame = ttkb.Frame(frame)
        type_frame.pack(fill=X, pady=5)

        ttkb.Radiobutton(type_frame, text="十六进制", variable=self.find_type,
                         value="hex").pack(side=LEFT, padx=10)
        ttkb.Radiobutton(type_frame, text="ASCII", variable=self.find_type,
                         value="ascii").pack(side=LEFT, padx=10)

        # 查找内容输入
        ttkb.Label(frame, text="查找内容:").pack(anchor=W, pady=2)

        self.find_entry = ttkb.Entry(frame, width=40)
        self.find_entry.pack(fill=X, pady=2)
        self.find_entry.focus_set()

        # 查找按钮
        btn_frame = ttkb.Frame(frame)
        btn_frame.pack(fill=X, pady=10)

        ttkb.Button(btn_frame, text="查找下一个", command=self.find_next).pack(side=LEFT, padx=5)
        ttkb.Button(btn_frame, text="取消", command=self.find_window.destroy).pack(side=LEFT, padx=5)

        # 绑定Enter键
        self.find_window.bind("<Return>", lambda event: self.find_next())

    def find_next(self):
        """执行查找"""
        if not self.file_content:
            messagebox.showinfo("提示", "请先打开一个文件")
            return

        search_text = self.find_entry.get().strip()
        if not search_text:
            return

        try:
            # 根据类型处理查找内容
            if self.find_type.get() == "hex":
                # 处理十六进制查找
                # 移除所有空格
                search_text = search_text.replace(" ", "")
                # 确保长度为偶数
                if len(search_text) % 2 != 0:
                    messagebox.showerror("错误", "十六进制查找内容长度必须为偶数")
                    return

                # 转换为字节
                search_bytes = bytes.fromhex(search_text)

            else:  # ascii
                # 处理ASCII查找
                search_bytes = search_text.encode('utf-8')

            # 从当前位置开始查找
            start_pos = self.find_pos
            result_pos = self.file_content.find(search_bytes, start_pos)

            if result_pos != -1:
                # 找到匹配项，计算所在行
                line_number = result_pos // self.bytes_per_line + 1

                # 滚动到找到的行
                self.hex_area.see(f"{line_number}.0")
                self.ascii_area.see(f"{line_number}.0")
                self.line_numbers.see(f"{line_number}.0")

                # 更新下次查找的起始位置
                self.find_pos = result_pos + 1

                # 在状态栏显示找到的位置
                self.status_bar.config(text=f"找到匹配项，位置: 0x{result_pos:X}")
            else:
                # 未找到，从头开始
                messagebox.showinfo("查找", f"找不到 '{search_text}'")
                self.find_pos = 0

        except Exception as e:
            messagebox.showerror("错误", f"查找失败: {str(e)}")

    def change_theme(self):
        """更改应用主题"""
        theme = self.selected_theme.get()
        style = ttkb.Style()
        style.theme_use(theme)

        # 更新行号背景色以匹配新主题
        # self.line_numbers.config(background=self.cget("bg"))

    def create_widgets(self):
        pass
